/*
 * Copyright (c) 2011-2023, ThinkWide (cnet2001@163.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.thinkwide.data.ormapping.db;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.DataSources;
import com.thinkwide.data.core.MFConvert;
import com.thinkwide.data.core.MFPropperties;
import com.thinkwide.data.core.MFString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 数据库连接池
 *
 * @author andyzhang
 * @since 2010-12-29
 */
public class MFConnectionPool {
    private static Logger logger = LoggerFactory.getLogger(MFConnectionPool.class);
    private static MFConnectionPool instance;
    private static MFConnectionInfo activeDataSource;
    public static final String DEFAULT = "default";
    public static final String DRIVE_CLASSES = "driverClasses";
    public static final String ADAPTER_CLASSES = "adapterClasses";
    public static final String DBTYPE = "dbType";
    public static final String URL = "url";
    public static final String USER = "user";
    public static final String PASSWORD = "password";
    public static final String DB_VERSION = "dbVersion";

    public static final String CHARSET = "charset";
    public static final String INITIALPOOLSIZE = "initialPoolSize";
    public static final String MAX_IDLETIME = "maxIdleTime";
    public static final String MAX_POOLSIZE = "maxPoolSize";
    public static final String MIN_POOLSIZE = "minPoolSize";
    public static final String ACQUIRE_INCREMENT = "acquireIncrement";
    public static final String CHECKOUT_TIMEOUT = "checkoutTimeout";
    public static final String AUTOCOMMIT_ONCLOSE = "autoCommitOnClose";
    public static final String TESTCONNECTIONONCHECKIN = "testConnectionOnCheckin";
    public static final String IDLECONNECTIONTESTPERIOD = "idleConnectionTestPeriod";

    private Map<String, MFConnectionInfo> poolList = null;
    private MFPropperties prop = null;

    private boolean propLoad = true;

    public boolean getPropLoad() {
        return propLoad;
    }

    public void setPropLoad(boolean value) {
        propLoad = value;
    }

    public static final MFConnectionPool getInstance() throws Throwable {
        if (instance == null) {
            instance = new MFConnectionPool();
        }
        return instance;
    }

    public synchronized final boolean hasConnection(String name) throws Throwable {
        return poolList.containsKey(name);
    }

    public synchronized final boolean containsConnection(String name) throws Throwable {
        return poolList.containsKey(name);
    }

    public boolean createConnectionDynamicDataSource(MFConnectionInfo connectionInfo) throws Throwable {
        poolList.put(connectionInfo.getName(), connectionInfo);
        return true;
    }

    public boolean createConnectionDynamicComboPooledDataSource(MFConnectionInfo connectionInfo) throws Throwable {
        ComboPooledDataSource cds = new ComboPooledDataSource();
        cds.setDataSourceName(connectionInfo.getName());
        cds.setDriverClass(connectionInfo.getDriverClasses());
        cds.setJdbcUrl(connectionInfo.getUrl());
        cds.setUser(connectionInfo.getUser());
        cds.setPassword(connectionInfo.getPassword());
        cds.setInitialPoolSize(connectionInfo.getInitialPoolSize());
        cds.setMaxPoolSize(connectionInfo.getMaxPoolSize());
        cds.setMinPoolSize(connectionInfo.getMinPoolSize());
        cds.setMaxIdleTime(connectionInfo.getMaxIdleTime());
        cds.setAcquireIncrement(connectionInfo.getAcquireIncrement());
        cds.setCheckoutTimeout(connectionInfo.getCheckoutTimeout());

        cds.setAutoCommitOnClose(connectionInfo.isAutoCommitOnClose());
        cds.setTestConnectionOnCheckin(connectionInfo.isTestConnectionOnCheckin());
        cds.setIdleConnectionTestPeriod(connectionInfo.getIdleConnectionTestPeriod());

        cds.setBreakAfterAcquireFailure(false);
        cds.setTestConnectionOnCheckout(false);
        cds.setTestConnectionOnCheckin(false);

        connectionInfo.setActiveDataSource(cds);
        poolList.put(connectionInfo.getName(), connectionInfo);
        return true;
    }

    public boolean createConnection(String name) throws Throwable {
        String url = prop.getPropValue(name + "." + URL);

        String AdapterClasses = prop.getPropValue(name + "." + ADAPTER_CLASSES);
        String DbType = prop.getPropValue(name + "." + DBTYPE);
        String DbVersion = prop.getPropValue(name + "." + DB_VERSION);
        String user = prop.getPropValue(name + "." + USER);
        String pwd = prop.getPropValue(name + "." + PASSWORD);
        String DC = prop.getPropValue(name + "." + DRIVE_CLASSES);
        String Charset = prop.getPropValue(name + "." + CHARSET);

        Integer Initialpoolsize = MFConvert.strToIntDef(prop.getPropValue(name + "." + INITIALPOOLSIZE), 8);
        Integer Maxidletime = MFConvert.strToIntDef(prop.getPropValue(name + "." + MAX_IDLETIME), 6000);
        Integer Maxpoolsize = MFConvert.strToIntDef(prop.getPropValue(name + "." + MAX_POOLSIZE), 6000);
        Integer Minpoolsize = MFConvert.strToIntDef(prop.getPropValue(name + "." + MIN_POOLSIZE), 8);
        Integer Acquireincrement = MFConvert.strToIntDef(prop.getPropValue(name + "." + ACQUIRE_INCREMENT), 10);
        Integer Checkouttimeout = MFConvert.strToIntDef(prop.getPropValue(name + "." + CHECKOUT_TIMEOUT), 3600);

        boolean AutoCommitOnClose = MFConvert.strToBooleanDef(prop.getPropValue(name + "." + AUTOCOMMIT_ONCLOSE), true);
        boolean TestConnectionOnCheckin = MFConvert.strToBooleanDef(prop.getPropValue(name + "." + TESTCONNECTIONONCHECKIN), false);
        Integer IdleConnectionTestPeriod = MFConvert.strToIntDef(prop.getPropValue(name + "." + IDLECONNECTIONTESTPERIOD), 60);

        ComboPooledDataSource cds = new ComboPooledDataSource();
        cds.setDataSourceName(name);
        cds.setDriverClass(DC);
        cds.setJdbcUrl(url);
        cds.setUser(user);
        cds.setPassword(pwd);
        cds.setInitialPoolSize(Initialpoolsize);
        cds.setMaxPoolSize(Maxpoolsize);
        cds.setMinPoolSize(Minpoolsize);
        cds.setMaxIdleTime(Maxidletime);
        cds.setAcquireIncrement(Acquireincrement);
        cds.setCheckoutTimeout(Checkouttimeout);

        cds.setAutoCommitOnClose(AutoCommitOnClose);
        cds.setTestConnectionOnCheckin(TestConnectionOnCheckin);
        cds.setIdleConnectionTestPeriod(IdleConnectionTestPeriod);

        cds.setBreakAfterAcquireFailure(false);
        cds.setTestConnectionOnCheckout(false);
        cds.setTestConnectionOnCheckin(false);
        //cds.setIdleConnectionTestPeriod(60);
        //cds.setAcquireRetryAttempts(10);
        //cds.setAcquireRetryDelay(1000);

        //cds.setAcquireRetryAttempts(10);
        //cds.setAcquireRetryDelay(1000);
        //cds.setTestConnectionOnCheckout(true);
        //cds.setBreakAfterAcquireFailure(false);

        MFConnectionInfo info = new MFConnectionInfo();
        info.setActiveDataSource(cds);
        info.setDbType(DbType);
        info.setDbVersion(DbVersion);
        info.setDbName(name);
        info.setAdapterClasses(AdapterClasses);
        info.setDbType(DbType);
        info.setDriverClasses(DC);
        info.setPassword(pwd);
        info.setUrl(URL);
        info.setUser(USER);

        info.setCharset(Charset);

        info.setCheckoutTimeout(Checkouttimeout);
        info.setMaxIdleTime(Maxidletime);
        info.setInitialPoolSize(Initialpoolsize);
        info.setMinPoolSize(Minpoolsize);
        info.setMaxPoolSize(Maxpoolsize);
        info.setAcquireIncrement(Acquireincrement);

        info.setAutoCommitOnClose(AutoCommitOnClose);
        info.setTestConnectionOnCheckin(TestConnectionOnCheckin);
        info.setIdleConnectionTestPeriod(IdleConnectionTestPeriod);

        poolList.put(name, info);
        return true;
    }

    public boolean checkAndLoadFromProp(String name) throws Throwable {
        if (poolList.containsKey(name)) {
            return true;
        } else {
            if (Objects.isNull(prop)) {
                prop = new MFPropperties("data.properties", false);
                prop.loadData();
            }
            String DC = prop.getPropValue(name + "." + DRIVE_CLASSES);

            if (MFString.notNullOrEmpty(DC)) {

                createConnection(name);
                return true;
            } else {
                logger.error(String.format("Not found %s data.properties", name + "." + DRIVE_CLASSES));
                return false;
            }
        }
    }

    public synchronized MFConnectionInfo getConnectionInfo(String name) {
        return poolList.get(name);
    }

    public void changeDefConnection(String name) throws Throwable {
        if (containsConnection(name)) {
            activeDataSource = poolList.get(name);
        }
    }

    protected MFConnectionPool() {

        poolList = new LinkedHashMap<String, MFConnectionInfo>();
		/*
		ds = new ComboPooledDataSource();
		MFPropperties prop=new MFPropperties("data.properties",true);
		String DC=prop.getPropValue("driverClasses");
		String URL=prop.getPropValue("url");
		ds.setDriverClass(DC);
		ds.setJdbcUrl(URL);
		ds.setUser(prop.getPropValue("user"));
		ds.setPassword(prop.getPropValue("password"));
		
		//初始化时获取三个连接，取值应在minPoolSize与maxPoolSize之间。Default: 3 initialPoolSize
		ds.setInitialPoolSize(8);
		//连接池中保留的最大连接数。Default: 15 maxPoolSize
		ds.setMaxPoolSize(300);
		//// 连接池中保留的最小连接数。
		ds.setMinPoolSize(8);
		//当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 acquireIncrement
		ds.setAcquireIncrement(10);
		
		//每60秒检查所有连接池中的空闲连接。Default: 0  idleConnectionTestPeriod
		ds.setIdleConnectionTestPeriod(60);
		//最大空闲时间,25000秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0  maxIdleTime
		ds.setMaxIdleTime(25000);
		//连接关闭时默认将所有未提交的操作回滚。Default: false autoCommitOnClose
		ds.setAutoCommitOnClose(true);
		
		//定义所有连接测试都执行的测试语句。在使用连接测试的情况下这个一显著提高测试速度。注意：
		//测试的表必须在初始数据源的时候就存在。Default: null  preferredTestQuery
		//ds.setPreferredTestQuery("select sysdate from dual");
		// 因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的
		// 时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable
		// 等方法来提升连接测试的性能。Default: false testConnectionOnCheckout
		//ds.setTestConnectionOnCheckout(true);
		//如果设为true那么在取得连接的同时将校验连接的有效性。Default: false  testConnectionOnCheckin
		ds.setTestConnectionOnCheckin(false);
		*/

    }

    public synchronized final Connection getConnection(String name) throws Throwable {
        try {
            if (containsConnection(name)) {
                MFConnectionInfo info = poolList.get(name);
                return info.getActiveDataSource().getConnection();
            } else {
                logger.error("Not Found:" + name);
                return null;
            }
        } catch (SQLException e) {
            logger.error(e.getMessage());
        }
        return null;
    }

    public synchronized final Connection getConnection() throws Throwable {
        try {
            //activeDataSource.setDataSourceName(dataSourceName)
            return activeDataSource.getActiveDataSource().getConnection();
        } catch (SQLException e) {
            logger.error(e.getMessage());
        }
        return null;
    }

    public synchronized final MFConnectionInfo getConnectionInfo() {

        return activeDataSource;

    }

    @Override
    protected void finalize() throws Throwable {
        for (Map.Entry<String, MFConnectionInfo> item : poolList.entrySet()) {
            DataSources.destroy(item.getValue().getActiveDataSource());
        }

        super.finalize();
    }
}
